/*
 * Copyright (C) 2016 Lokiy(liulongke@gmail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  　　　　http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.lokiy.core.vm

import android.text.TextUtils
import androidx.lifecycle.*
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlin.coroutines.CoroutineContext

/**
 * 启动协程并且返回 LiveData
 * <pre>
 *     suspend fun dataList() = flow {
 *              val cacheData: List<BottomListItem>? = dataSourceByDb.dataList
 *              if (cacheData?.isNotEmpty() == true) {
 *                  emit(cacheData)
 *              }
 *              val netData = dataSourceByNet.dataList
 *              if (netData?.isNotEmpty() == true) {
 *                  dataSourceByDb.dataList = netData
 *                  emit(netData)
 *              }
 *          }.flowOn(Dispatchers.IO)
 * </pre>

 * @param block 最好返回 Flow<T>
 * @return LiveData<T>
 *
 *
 *
 */
inline fun <reified T> ViewModel.liveDataOf(
    context: CoroutineContext = viewModelScope.coroutineContext + Dispatchers.Main,
    crossinline onError: (Throwable) -> String? = {
        it.printStackTrace()
        ""
    },
    crossinline block: suspend LiveDataScope<T>.() -> Any,
): LiveData<T> {
    return liveData(context + CoroutineExceptionHandler { _, throwable ->
        val handle = kotlin.runCatching { onError.invoke(throwable) }.onFailure { it.printStackTrace() }.getOrNull()
        if (handle?.isNotEmpty() == true) {
            toastLiveData.value = handle
        }
        isLoading.postValue(false)
    }) {
        val scope = object : LiveDataScope<T> {
            override val latestValue: T?
                get() = this@liveData.latestValue

            override suspend fun emit(value: T) {
                isLoading.postValue(false)
                this@liveData.emit(value)
            }

            override suspend fun emitSource(source: LiveData<T>): DisposableHandle {
                isLoading.postValue(false)
                return this@liveData.emitSource(source)
            }
        }
        val result = scope.block()
        if (result is Flow<*>) {
            result.collect {
                if (it is T) {
                    emit(it)
                }
            }
        }
    }
}


/**
 * safety launch a coroutine
 */
inline fun ViewModel.launch(
    crossinline onError: (Throwable) -> String? = {
        it.printStackTrace()
        ""
    },
    crossinline block: suspend CoroutineScope.() -> Unit
) =
    viewModelScope.launch(viewModelScope.coroutineContext + Dispatchers.Main + CoroutineExceptionHandler { _, throwable ->
        val handle = kotlin.runCatching { onError.invoke(throwable) }.onFailure { it.printStackTrace() }.getOrNull()
        if (handle?.isNotEmpty() == true) {
            toastLiveData.value = handle
        }
        isLoading.postValue(false)
    }) {
        block()
        isLoading.postValue(false)
    }